package cn.cloudtogo.server.atomapi;

import cn.cloudtogo.common.Assert;
import cn.cloudtogo.common.GendbException;
import cn.cloudtogo.common.atomapi.token.ApiToken;
import cn.cloudtogo.common.atomapi.token.ApiTokenService;
import cn.cloudtogo.common.model.ApiTokenDao;
import cn.cloudtogo.common.model.ApiTokenModel;
import cn.cloudtogo.server.atomapi.event.GlobalApiTokenCreatedEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Objects;

/**
 * @author yaolianhua789@gmail.com
 **/
@Slf4j
@Service
public class GendbApiTokenService {

    private final ApiTokenService apiTokenService;
    private final ApiTokenDao apiTokenDao;
    private final ApiTokenObjectConverterHelper apiTokenObjectConverterHelper;
    private final ApplicationEventPublisher eventPublisher;

    public GendbApiTokenService(ApiTokenService apiTokenService,
                                ApiTokenDao apiTokenDao,
                                ApiTokenObjectConverterHelper apiTokenObjectConverterHelper,
                                ApplicationEventPublisher eventPublisher) {
        this.apiTokenService = apiTokenService;
        this.apiTokenDao = apiTokenDao;
        this.apiTokenObjectConverterHelper = apiTokenObjectConverterHelper;
        this.eventPublisher = eventPublisher;
    }

    public synchronized void reset(String uuid, String notes, boolean async) {
        ApiTokenModel oldToken = apiTokenDao.query(uuid, notes);

        this.remove(uuid, notes);
        ApiTokenCreateParam ofGlobal = ApiTokenCreateParam.ofGlobal(uuid, notes);
        ofGlobal.setDisable(oldToken.isDisable());

        if (async) {
            eventPublisher.publishEvent(new GlobalApiTokenCreatedEvent(ofGlobal));
        } else {
            ApiToken apiToken = this.generate(ofGlobal);
            log.info("[{}] Api token generated for datasource [{}]", apiToken.getNotes(), uuid);

        }
    }

    public synchronized void remove(String uuid, String notes) {
        Assert.argument(StringUtils.hasText(uuid), () -> "uuid is required");
        Assert.argument(StringUtils.hasText(notes), () -> "token name is required");
        ApiTokenModel model = apiTokenDao.query(uuid, notes);

        Assert.notNull(model, () -> String.format("Api token '%s' was not found '%s'", notes, uuid), 404);

        boolean removed = apiTokenService.remove(ApiToken.of(String.format("%s %s", model.getType(), model.getToken())));
        if (removed) {
            apiTokenDao.delete(uuid, notes);
            log.info("Api token removed successful");
            return;
        }

        throw new GendbException(String.format("Remove api token failure '%s'", uuid));
    }

    public void removeAll(String uuid) {
        Assert.argument(StringUtils.hasText(uuid), () -> "uuid is required");
        List<ApiTokenModel> models = apiTokenDao.list(uuid);
        for (ApiTokenModel model : models) {
            try {
                remove(uuid, model.getNotes());
            } catch (Exception e) {
                //
            }

        }
    }

    public synchronized ApiToken generate(ApiTokenCreateParam request) {
        Assert.notNull(request, () -> "request is null");
        Assert.argument(StringUtils.hasText(request.getUuid()), () -> "uuid is required");
        Assert.argument(StringUtils.hasText(request.getNotes()), () -> "token name is required");

        ApiTokenModel exist = apiTokenDao.query(request.getUuid(), request.getNotes());
        if (null != exist) {
            log.info("Api token '{}' already exist. '{}'", request.getNotes(), request.getUuid());
            return ApiToken.of(exist.getNotes(), exist.getToken(), exist.getType(), null);
        }

        StopWatch obtainTokenWatch = new StopWatch();
        obtainTokenWatch.start();
        ApiToken token = apiTokenService.obtain(request.getNotes(), request.getResources());
        obtainTokenWatch.stop();
        double obtainTokenWatchTotalTimeSeconds = obtainTokenWatch.getTotalTimeSeconds();
        log.info("Get token from server. Takes '{}s'", obtainTokenWatchTotalTimeSeconds);

        if (token == null || !StringUtils.hasText(token.getToken())) {
            log.error("Get token null.");
            return ApiToken.of();
        }
        String json = apiTokenObjectConverterHelper.write(request);
        ApiTokenModel model = new ApiTokenModel();
        model.setUuid(request.getUuid());
        model.setNotes(request.getNotes());
        model.setDisable(request.isDisable());
        model.setJson(json);
        model.setToken(token.getToken());
        model.setType(token.getType());
        apiTokenDao.insert(model);

        return token;
    }

    public void switchToken(String uuid, String notes, String onOff) {
        Assert.argument(StringUtils.hasText(uuid), () -> "uuid is required");
        Assert.argument(StringUtils.hasText(notes), () -> "token name is required");
        Assert.argument(StringUtils.hasText(onOff), () -> "token switch is required");

        boolean contains = List.of("on", "off").contains(onOff);
        if (!contains) {
            throw new GendbException("token switch value [on,off]", 400);
        }
        ApiTokenModel model = apiTokenDao.query(uuid, notes);
        Assert.notNull(model, () -> String.format("Api token '%s' was not found '%s'", notes, uuid), 404);
        boolean disable = Objects.equals("off", onOff);
        apiTokenDao.update(uuid, notes, disable);
    }
}
